package drds.global_transaction.spring.annotation;

import drds.common.ShouldNeverHappenException;
import drds.global_transaction.global_transaction_manager.api.*;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import java.lang.reflect.Method;

@Slf4j
public class GlobalTransactionalInterceptor implements MethodInterceptor
{
	private final GlobalTransactionTemplate globalTransactionTemplate = new GlobalTransactionTemplate();
	private final GlobalTransactionFailureHandler globalTransactionFailureHandler;

	public GlobalTransactionalInterceptor(GlobalTransactionFailureHandler globalTransactionFailureHandler)
	{
		if (null == globalTransactionFailureHandler)
		{
			globalTransactionFailureHandler = new DefaultGlobalTransactionFailureHandlerImpl();
		}
		this.globalTransactionFailureHandler = globalTransactionFailureHandler;
	}

	private String createMethodName(Method method)
	{
		StringBuilder sb = new StringBuilder();

		String methodName = method.getName();
		Class<?>[] params = method.getParameterTypes();
		sb.append(methodName);
		sb.append("(");
		int index = 0;
		for (Class<?> clazz : params)
		{
			sb.append(clazz.getName());
			if (++index < params.length)
			{
				sb.append(",");
			}
		}
		sb.append(")");
		return sb.toString();
	}

	private GlobalTransaction getAnnotation(Method method)
	{
		if (method == null)
		{
			return null;
		}
		return method.getAnnotation(GlobalTransaction.class);
	}

	@Override
	public Object invoke(final MethodInvocation methodInvocation) throws Throwable
	{
		final GlobalTransaction globalTransaction = getAnnotation(methodInvocation.getMethod());
		if (globalTransaction != null)
		{
			try
			{
				return globalTransactionTemplate.execute(new GlobalTransactionExecutor()
				{
					@Override
					public Object execute() throws Throwable
					{
						return methodInvocation.proceed();
					}

					@Override
					public String name()
					{
						if (globalTransaction.name() != null)
						{
							return globalTransaction.name();
						}
						return createMethodName(methodInvocation.getMethod());
					}

					@Override
					public int timeout()
					{
						return globalTransaction.timeoutMilliseconds();
					}
				});
			} catch (GlobalTransactionExecutionException e)
			{
				GlobalTransactionExecutionExceptionCode globalTransactionExecutionExceptionCode = e.getGlobalTransactionExecutionExceptionCode();
				switch (globalTransactionExecutionExceptionCode)
				{
				case rollback_global_transaction_done:
					throw e.getOriginalException();
				case begin_global_transaction_failure:
					globalTransactionFailureHandler.onGlobalTransactionBeginFailure(e.getGlobalTransaction(), e.getCause());
					throw e.getCause();
				case commit_global_transaction_failure:
					globalTransactionFailureHandler.onGlobalTransactionCommitFailure(e.getGlobalTransaction(), e.getCause());
					throw e.getCause();
				case rollback_global_transaction_failure:
					globalTransactionFailureHandler.onGlobalTransactionRollbackFailure(e.getGlobalTransaction(), e.getCause());
					throw e.getCause();
				default:
					throw new ShouldNeverHappenException("unknown GlobalTransactionExecutor" + ".GlobalTransactionExecutionExceptionCode: " + globalTransactionExecutionExceptionCode);

				}
			}

		}
		return methodInvocation.proceed();
	}
}
